package com.echo.code.thread.lock.impl;

import com.echo.code.thread.lock.Lock;

import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeoutException;
import java.util.logging.Logger;

import static java.lang.System.currentTimeMillis;

/**
 * SimpleLock Created by baiyap on 2018/9/24.
 */
public class SimpleLock implements Lock {

  private static Logger log = Logger.getLogger(SimpleLock.class.getName());
  private Set<Thread> blockedSet = new HashSet<>();
  private boolean locked = false;
  private Thread currentThread;

  @Override
  public void lock() throws InterruptedException {
    Thread thread = Thread.currentThread();
    synchronized (this) {
      while (locked) {
        blockedSet.add(thread);
        log.info(getCurrentThreadName() + " can not get the lock and suspend...");
        try {
          this.wait();
        } catch (InterruptedException ite) {
          blockedSet.remove(thread);
          throw ite;
        }
      }
      blockedSet.remove(thread);
      locked = true;
      currentThread = thread;
      log.info(getCurrentThreadName() + " obtain the lock.");
    }
  }

  @Override
  public void lock(long millis) throws InterruptedException, TimeoutException {
    synchronized (this) {
      if (millis <= 0l) {
        this.lock();
      } else {
        Thread thread = Thread.currentThread();
        long remainMillis = millis;
        long endMillis = currentTimeMillis() + remainMillis;
        while (locked) {
          if (remainMillis <= 0) {
            blockedSet.remove(thread);
            throw new TimeoutException();
          }
          blockedSet.add(thread);
          log.info(getCurrentThreadName() + " can not get the lock and suspend...");
          try {
            this.wait(remainMillis);
          } catch (InterruptedException ite) {
            blockedSet.remove(thread);
            throw ite;
          }
          remainMillis = endMillis - currentTimeMillis();
        }
        blockedSet.remove(thread);
        locked = true;
        currentThread = thread;
        log.info(getCurrentThreadName() + " obtain the lock.");
      }
    }
  }

  @Override
  public void unlock() {
    synchronized (this) {
      if (Objects.equals(currentThread, Thread.currentThread())) {
        this.locked = false;
        this.notifyAll();
        log.info(getCurrentThreadName() + " release the lock.");
      }
    }
  }
}
